package ops

import (
	"bytes"
	. "com.mgface.disobj/apinode/api"
	"com.mgface.disobj/apinode/objstream"
	. "com.mgface.disobj/common"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/klauspost/reedsolomon"
	log "github.com/sirupsen/logrus"
	"io"
	"io/ioutil"
	"net/http"
	"runtime"
	"time"
)

//1.算出请求数据的sha256
//
//2.读取请求的数据
//
//3.创建连接到MQ的客户端
//
//4.执行查询文件的操作，判断文件是否存在
func storeObject(reqData io.Reader, objName string) (int, error) {
	//算出请求数据的整体hash值
	hash := sha256.New()
	readData, _ := ioutil.ReadAll(reqData)
	hash.Write(readData)
	hashInBytes := hash.Sum(nil)
	hashValue := hex.EncodeToString(hashInBytes)

	client := NewReconTCPClient(MetaNodeAddr)
	cmd := &Cmd{Name: "get", Key: "filecrc", Value: hashValue}
	cmd.Run(client)
	if cmd.Error != nil {
		return http.StatusMethodNotAllowed, cmd.Error
	}
	//查询文件是否在元数据存在，元数据不存在，那么写入元数据
	if cmd.Value == "[]" {
		status, err := buildShared(readData, hashValue, objName)
		if status != http.StatusOK && err != nil {
			return status, err
		}
	}
	log.Debug("更新metadata......")
	//发出请求
	resp := make(chan error)
	go buildDesc(int64(len(readData)), objName, hashValue, client, resp)
	err := <-resp
	if err != nil {
		return http.StatusInternalServerError, err
	}

	return http.StatusOK, nil
}

//构建文件上传的描述符
func buildDesc(datasize int64, objName, hashValue string, client *TcpClient, resp chan error) {

	digest := Datadigest{
		Index:     time.Now().Unix(),
		Hash:      hashValue,
		Version:   1,
		Datasize:  datasize,
		Created:   time.Now(),
		FileOwner: "admin",
	}

	dkv := &DataKeyValue{
		Data: make(map[string][]Datadigest),
	}
	datadigests := make([]Datadigest, 0)
	dkv.Data[objName] = append(datadigests, digest)
	data, _ := json.Marshal(dkv)
	cmd := &Cmd{Name: "set", Key: "metadata", Value: string(data)}
	cmd.Run(client)
	resp <- cmd.Error
}

//构建数据分片
func buildShared(readData []byte, hashValue, objName string) (int, error) {
	//读取splitData数组，前面4个数据为数据分片，后面2个为奇偶校验分片
	rsencoder, _ := reedsolomon.New(DataShards, ParityShards, reedsolomon.WithMaxGoroutines(runtime.NumCPU()))
	splitData, _ := rsencoder.Split(readData)
	result := make([]error, 0)

	expectIps := make([]string, 0)

	//todo 可以启用多个goroutine运行，并且需要考虑如果集中提交没有成功， 是否需要回退提交的部分数据
	for index, vdata := range splitData {
		//进行gzip压缩
		gzipdata, _ := GzipEncode(vdata)
		reader := bytes.NewReader(gzipdata)
		var stream *objstream.PutStream
		var err error
		stream, expectIps, err = putStream(hashValue, objName, index, expectIps) //expectIps 去重
		if err != nil {
			return http.StatusServiceUnavailable, err
		}
		//把数据copy进内存管道
		io.Copy(stream, reader)
		//关闭写通道
		err = stream.Close()
		result = append(result, err)
	}
	//返回结果要和切片总数相等
	if len(result) != (DataShards + ParityShards) {
		err := errors.New(fmt.Sprintf("数据分片返回的结果数不相等，返回:%d,期望:%d", len(result), DataShards+ParityShards))
		return http.StatusInternalServerError, err
	} else {
		for _, err := range result {
			if err != nil {
				return http.StatusInternalServerError, err
			}
		}
	}
	return http.StatusOK, nil
}
